package com.igoal.service;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.LogFactory;
import org.hibernate.criterion.MatchMode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import com.igoal.dao.CategoryDAO;
import com.igoal.dao.CommentEntryDoneDAO;
import com.igoal.dao.GoalCategoryDAO;
import com.igoal.dao.GoalDAO;
import com.igoal.dao.MustDoGoalDAO;
import com.igoal.dao.UserDAOExt;
import com.igoal.dao.UserGoalDAO;
import com.igoal.dto.GoalDTO;
import com.igoal.dto.UserDTO;
import com.igoal.entity.Category;
import com.igoal.entity.DataStatus;
import com.igoal.entity.Goal;
import com.igoal.entity.GoalCategory;
import com.igoal.entity.GoalStatusEnum;
import com.igoal.entity.MustDoGoal;
import com.igoal.entity.User;
import com.igoal.entity.UserGoal;
import com.igoal.entity.factory.EntityFactory;
import com.igoal.service.paging.PagingService;
import com.igoal.utils.WebUtils;
import com.igoal.web.Constants;

@Service("goalManager")
public class GoalManagerImpl extends BaseService<GoalDTO, Goal, Long>
		implements GoalManager {
	@Qualifier("userDAOExt")
	private UserDAOExt userDAO;
	private CommentEntryDoneDAO commentEntryDoneDAO;
	private GoalDAO goalDAO;
	private UserGoalDAO userGoalDAO;
	private CategoryDAO categoryDAO;
	private GoalCategoryDAO goalCategoryDAO;
	private MustDoGoalDAO mustDoGoalDAO;

	public GoalManagerImpl() {
		logger = LogFactory.getLog(GoalManagerImpl.class);
	}

	@Autowired
	public void setMustDoGoalDAO(MustDoGoalDAO mustDoGoalDAO) {
		this.mustDoGoalDAO = mustDoGoalDAO;
	}
	
	
	@Autowired
	public void setCategoryDAO(CategoryDAO categoryDAO) {
		this.categoryDAO = categoryDAO;
	}

	@Autowired
	public void setGoalCategoryDAO(GoalCategoryDAO goalCategoryDAO) {
		this.goalCategoryDAO = goalCategoryDAO;
	}

	@Autowired
	public void setUserDAO(UserDAOExt userDAO) {
		this.userDAO = userDAO;
	}

	@Autowired
	public void setCommentEntryDoneDAO(CommentEntryDoneDAO commentEntryDoneDAO) {
		this.commentEntryDoneDAO = commentEntryDoneDAO;
	}

	public CommentEntryDoneDAO getCommentEntryDoneDAO() {
		return commentEntryDoneDAO;
	}

	@Autowired
	public void setGoalDAO(GoalDAO goalDAO) {
		this.goalDAO = goalDAO;
	}

	@Autowired
	public void setUserGoalDAO(UserGoalDAO userGoalDAO) {
		this.userGoalDAO = userGoalDAO;
	}

	public GoalDTO getGoalByPrimaryKey(Long id) {
		GoalDTO dto = new GoalDTO();
		Goal entity = get(id);
		if (entity != null) {
			dto.initFrom(entity);
			return dto;
		}
		return null;
	}

	public boolean exists(Long id) {
		return goalDAO.exists(id);
	}

	public Goal get(Long id) {
		Goal goal = goalDAO.get(id);
		return goal;
	}

	public List<Goal> getAll() {
		return goalDAO.getAllDistinct();
	}

	public void remove(Long id) {
		goalDAO.remove(id);
	}

	public Goal save(Goal object) {
		return goalDAO.save(object);
	}

	public List<GoalDTO> getCommonGoals(UserDTO user, int numberOfGoals) {
		List<Goal> entities = goalDAO.findTopGoals(numberOfGoals);
		if (entities != null) {
			List<GoalDTO> goals = new ArrayList<GoalDTO>();
			for (Goal entity : entities) {
				GoalDTO goal = new GoalDTO();
				goal.initFrom(entity);
				goals.add(goal);
			}
			return goals;
		}
		return null;
	}

	public List<GoalDTO> getCommonGoals(UserDTO user) {
		return getCommonGoals(user, PagingService.MAX_COMMON_GOALS);
	}

	public List<GoalDTO> getTopGoals(UserDTO user) {
		return getCommonGoals(user, PagingService.MAX_TOP_GOALS);
	}

	public int countDoUsers(Long goalId) {
		return userGoalDAO.countDoUsers(goalId);
	}

	public int countDoingUsers(Long goalId) {
		return userGoalDAO.countDoingUsers(goalId);
	}
	
	public int countDoneUsers(Long goalId) {
		return userGoalDAO.countDoneUsers(goalId);
	}

	public List<UserDTO> getRandomUsersDoGoal(Long goalId) {
		List<UserGoal> userGoals = userGoalDAO.getRandomUserDoGoal(goalId);
		List<UserDTO> users = new ArrayList<UserDTO>();
		for (UserGoal userGoal : userGoals) {
			UserDTO user = new UserDTO();
			user.initFrom(userGoal.getUser());
			users.add(user);
		}
		return users;
	}

	public List<UserDTO> getUsersDoGoal(Long goalId, int pageNumber) {
		List<UserGoal> userGoals = userGoalDAO.getUsersDoGoal(goalId, pageNumber);
		List<UserDTO> users = new ArrayList<UserDTO>();
		for (UserGoal userGoal : userGoals) {
			UserDTO user = new UserDTO();
			user.initFrom(userGoal.getUser());
			users.add(user);
		}
		return users;
	}
	
	public int addGoalToCategory(Long goalId, Long categoryId) {
		try {
			Goal goal = goalDAO.get(goalId);
			if (goal == null) {
				logger.info("Cannot find goal has id:" + goalId);
				return SaveResult.FAIL;
			}

			Category category = categoryDAO.get(categoryId);
			if (category == null) {
				logger.info("Cannot find category has id:" + categoryId);
				return SaveResult.FAIL;
			}

			return addGoalToCategory(goal, category);
		} catch (Exception e) {
			logger.error(e);
			return SaveResult.FAIL;
		}
	}

	public int addGoalToCategory(Long goalId, String categoryName) {
		try {
			Goal goal = goalDAO.get(goalId);
			if (goal == null) {
				logger.info("Cannot find goal has id:" + goalId);
				return SaveResult.FAIL;
			}

			Category category = categoryDAO.findByCategory(categoryName);
			if (category == null) {
				category = EntityFactory.createCategory(categoryName);
			}

			return addGoalToCategory(goal, category);
		} catch (Exception e) {
			logger.error(e);
			return SaveResult.FAIL;
		}
	}

	private int addGoalToCategory(Goal goal, Category category) {
		if (goal == null || category == null)
			return SaveResult.FAIL;

		boolean exists = goalCategoryDAO.exists(goal.getId(), category.getId());
		if (!exists) {
			try {
				GoalCategory goalCategory = EntityFactory.createGoalCategory(
						goal, category);
				goalCategoryDAO.save(goalCategory);
				return SaveResult.SUCCESS;
			} catch (Exception e) {
				logger.error(e);
				return SaveResult.FAIL;
			}
		} else {
			return SaveResult.EXIST;
		}
	}

	public int createNewCategory(String categoryName) {
		if (StringUtils.isBlank(categoryName))
			return SaveResult.FAIL;

		Category category = categoryDAO.findByCategory(categoryName);
		if (category == null) {
			logger.info("User requested create an exists category:"
					+ categoryName);
			return SaveResult.EXIST;
		}

		try {
			category = EntityFactory.createCategory(categoryName);
			categoryDAO.save(category);
			return SaveResult.SUCCESS;
		} catch (Exception e) {
			logger.error(e);
			return SaveResult.FAIL;

		}
	}

	public GoalStatusEnum getGoalStatus(Long goalId) {
		String username = getUserInSession();
		User user = null;

		try {
			user = (User) userDAO.loadUserByUsername(username);
		} catch (UsernameNotFoundException e) {
			return GoalStatusEnum.THINKING;
		}

		UserGoal userGoal = userGoalDAO.findByPK(user.getId(), goalId);
		if (userGoal == null) {
			return GoalStatusEnum.THINKING;
		} else {
			if (userGoal.getStatus().equals(DataStatus.DELETED)) {
				return GoalStatusEnum.THINKING;
			}
		}

		return userGoal.getGoalStatus();
	}

	public List<GoalDTO> findGoalByTitle(String title) {
		List<Goal> goals = goalDAO.findByTitle(title, MatchMode.ANYWHERE, -1);
		List<GoalDTO> result = new ArrayList<GoalDTO>();
		if (goals != null) {
			for (Goal goal : goals) {
				GoalDTO goalDTO = new GoalDTO();
				goalDTO.initFrom(goal);
				result.add(goalDTO);
			}
		}
		return result;
	}
	
	public GoalDTO getGoalByTitle(String title){
		List<Goal> goals = goalDAO.findByTitle(title, MatchMode.EXACT, 1);
		
		if (goals != null && !goals.isEmpty()) {
			Goal goal = goals.get(0);
			GoalDTO goalDTO = new GoalDTO();
			goalDTO.initFrom(goal);
			
			return goalDTO;
		}
		return null;
	}

	public List<GoalDTO> getShouldDoGoals(Long userId) {
		List<GoalDTO> result = new ArrayList<GoalDTO>();
		try{
			List<Goal> goals = goalDAO.getShouldDoGoals(Constants.MAX_YOU_SHOULD_DO_GOAL, userId);
			
			if (goals != null) {
				for (int i = 0; (i < goals.size()) && (i < Constants.MAX_YOU_SHOULD_DO_GOAL); i++) {
					GoalDTO goalDTO = new GoalDTO();
					Goal goal = goals.get(i);
					goalDTO.initFrom(goal);
					result.add(goalDTO);
				}
			}
		}catch (Exception e) {
			logger.error(e);
		}
		
		return result;
	}
	
	public GoalDTO getMustDoGoal(String username){
		try{
			User user = (User) userDAO.loadUserByUsername(username);			
			List<Goal> notInGoalList = new ArrayList<Goal>();
			
			Set<UserGoal> userGoals = user.getUserGoals();			
			if(userGoals != null){
				for(UserGoal userGoal : userGoals){
					notInGoalList.add(userGoal.getGoal());
				}
			}
			
			MustDoGoal mustDoGoal = mustDoGoalDAO.getRandomMustDoGoal(notInGoalList);
			if(mustDoGoal != null){
				Goal goal = mustDoGoal.getGoal();
				GoalDTO dto = new GoalDTO();
				dto.initFrom(goal);
				return dto;
			}
		}catch (Exception e) {
			logger.error(e);
		}
		
		return null;
	}
	
	public List<GoalDTO> getAllGoals(){
		List<Goal> goals = getAll();
		List<GoalDTO> goalDTOs = new ArrayList<GoalDTO>();		
		for(Goal goal : goals){
			GoalDTO dto = new GoalDTO();
			dto.initFrom(goal);
			goalDTOs.add(dto);
		}
		
		return goalDTOs;
	}
	
	public int disable(Long id){
		if(!goalDAO.exists(id)){
			return SaveResult.FAIL;
		}
		
		Goal goal = get(id);
		goal.setStatus(DataStatus.DELETED);
		goalDAO.save(goal);
		
		return SaveResult.SUCCESS;
	}
	
	public int enable(Long id){
		if(!goalDAO.exists(id)){
			return SaveResult.FAIL;
		}
		
		Goal goal = get(id);
		goal.setStatus(DataStatus.USING);
		goalDAO.save(goal);
		
		return SaveResult.SUCCESS;
	}
	
	public int countGoalsFrom(Date date){
		if(date == null){
			return getAll().size();
		}
		
		return goalDAO.countGoalsFrom(date);
	}
	
	public Set<GoalDTO> getRandomGoals(int maxGoals){
		List<Goal> entities = goalDAO.getRandomGoals(maxGoals);
		List<GoalDTO> result = new ArrayList<GoalDTO>();
		convertListEntitiesToDTOs(entities, result, GoalDTO.class);
		
		return WebUtils.buildGoalCloud(result);
	}
}
